//
//  TimedEvent.swift
//  Utility
//
//  Created by 何海涛 on 2017/5/24.
//  Copyright © 2017年 何海涛. All rights reserved.
//

import Foundation

// MARK: - EventTimer

/// 可重用的计时器。
/// 因为结构体内无法使用selector; 不得不重新构建中间件来解决这个问题。
/// - Note: iOS10 之后可以使用闭包来构建scheduledTimer。
fileprivate class EventTimer {
    
    fileprivate var interval: TimeInterval
    fileprivate var repeats: Bool
    fileprivate var timer: Timer?
    var timerFireClosure: ((Timer)->Void)?
    private var locker: NSLock = NSLock()
    
    var isValid: Bool { return timer?.isValid ?? false }
    
    /// 创建计时器
    ///
    /// - Parameters:
    ///   - interval: 间隔时间（秒）
    ///   - autoStart: 是否自动启动。默认`是`
    ///   - fireImmediate: 如果自动启动，启动后是否立即执行。默认`否`
    ///   - repeats: 是否反复执行。默认`是`
    init(interval: TimeInterval,
         autoStart: Bool = true,
         fireImmediate: Bool = false,
         repeats: Bool = true) {
        self.interval = interval
        self.repeats = repeats
        // init
        // start if needed.
        if autoStart { start(fireImmediate: fireImmediate) }
    }
    
    // auto release.
    deinit {
        print("deinit")
        stop()
    }
    
    /// 计时器工厂方法
    private func build() -> Timer {
        return Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(EventTimer.fire(timer:)), userInfo: nil, repeats: repeats)
    }
    
    /// 计时器触发响应方法
    ///
    /// - Parameter timer: 计时器
    @objc func fire(timer: Timer) {
        timerFireClosure?(timer)
    }
    
    /// 启动计时器（一定会启动成功，不用担心计时器作废的情况）
    ///
    /// - Parameter fireImmediate: 立即调用？是的话即刻响应回调方法。否的话等待interval间隔再响应。
    func start(fireImmediate: Bool) {
        // lock.
        locker.lock()
        defer { locker.unlock() }
        
        stop()
        timer = build()
        if fireImmediate { timer?.fire() }
    }
    
    /// 停止计时器
    func stop() {
        timer?.invalidate()
        timer = nil
    }
}

// MARK: - TimedEvent

/// 计时器事件。
/// - Note: 这里为什么不用`class` ? 是因为想保持 `var` 申明的事件变量才允许添加委托元素的特性。
/// - Note: `var` 类型的变量才允许调用 `mutating` 方法。
public struct TimedEvent<TTimeHandler: AnyObject>: IEvent {
    
    // - MARK: event
    public typealias Handler = TTimeHandler
    public var container: WeakableContainer<TTimeHandler> = WeakableContainer()
    
    // - MARK: timer
    private var timer: EventTimer
    private var thunk: ((TTimeHandler, Timer) -> Void)
    public var valid: Bool { return timer.isValid }
    public var interval: TimeInterval { return timer.interval }
    
    /// 创建计时器事件.
    
    /// - Parameters:
    ///   - interval: 间隔时间（秒）
    ///   - autoStart: 是否自动启动。默认`是`
    ///   - fireImmediate: 如果自动启动，启动后是否立即执行。默认`否`
    ///   - repeats: 是否反复执行。默认`是`
    ///   - handler: 事件委托
    ///   - timer: 触发的计时器
    /// - Note: 如果autoStart=true, 请在适当的地方调用`stop`来停止计时器以释放相关资源。
    public init(interval: TimeInterval,
                autoStart: Bool = true,
                fireImmediate: Bool = false,
                repeats: Bool = true,
                thunk: @escaping (_ handler: TTimeHandler,_ timer: Timer)->Void) {
        self.timer = EventTimer(interval: interval, autoStart: autoStart, fireImmediate: fireImmediate, repeats: repeats)
        self.thunk = thunk
    }
    
    /// 启动计时器
    /// - Parameter fireImmediate: 如果为`true`，立即触发多播；否则需要等待`interval`间隔才进行多播。
    /// - Note: 如果你通过该方法启动了计时器，那么你有必要在某处使用`stop`方法停止计时器以释放相关资源。
    public func start(fireImmediate: Bool = false) {
        // init timer.
        timer.start(fireImmediate: fireImmediate)
        refreshClosure()
    }
    
    /// 停止计时器。如果需要释放Timer，必须调用这个方法。
    /// - Note: 如果之后再调用`start`方法，计时器又会启用.
    public func stop() {
        timer.stop()
        refreshClosure()
    }
    
    /// 更新timer闭包。
    fileprivate func refreshClosure() {
        // 如果计时器无效，直接释放闭包，同时也将释放闭包持有的资源。
        //（如：EventTimer）,看下面的赋值逻辑。
        guard timer.isValid else {
            timer.timerFireClosure = nil
            return
        }
        // 因为闭包捕获变量的局限性，每次timerFireClosure的赋值都会将当前注册的消费者快照一份。
        timer.timerFireClosure = { timer in
            self.invoke({ (handler) in
                self.thunk(handler, timer)
            })
        }
    }
    
    /// 重写：移除所有事件元素
    public mutating func clear() {
        container.removeAll()
        refreshClosure()
    }
}

/// 重写：添加强引用
public func +=<T>(lhs: inout TimedEvent<T>, rhs: T) {
    lhs.container.add(element: rhs)
    lhs.refreshClosure()
}

/// 重写：添加弱引用
public func +=?<T>(lhs: inout TimedEvent<T>, rhs: T) {
    lhs.container.addWeak(element: rhs)
    lhs.refreshClosure()
}

/// 重写：删除引用
public func -=<T>(lhs: inout TimedEvent<T>, rhs: T) {
    lhs.container.remove(element: rhs)
    lhs.refreshClosure()
}
